package dao;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import model.Location;
import model.Process;
import model.ProductType;
import model.Stack;

public class Dao {

	private static Dao dao;

	private List<ProductType> productTypes;
	private List<Process> processes;
	private List<Stack> stacks;
	private List<Location> locations;

	private int stackWidth;
	private int stackHeight;
	private EntityManagerFactory emf = Persistence
			.createEntityManagerFactory("Carletti");
	private EntityManager em = emf.createEntityManager();

	/**
	 * Private constructor. Used for Singleton pattern.
	 * 
	 * @author Alexander Shidlovsky
	 */
	private Dao() {
		productTypes = new ArrayList<ProductType>();
		processes = new ArrayList<Process>();
		stacks = new ArrayList<Stack>();
		locations = new ArrayList<Location>();
		stackWidth = 5;
		stackHeight = 5;

		updateLocalStorage();
	}

	/**
	 * Gets instance of Dao. Used for Singleton pattern
	 * 
	 * @author Alexander Shidlovsky
	 * @return
	 */
	public static Dao getInstance() {
		if (dao == null) {
			dao = new Dao();
		}
		return dao;
	}

	public int getStackWidth() {
		return stackWidth;
	}

	public void setStackWidth(int stackWidth) {
		this.stackWidth = stackWidth;
	}

	public int getStackHeight() {
		return stackHeight;
	}

	public void setStackHeight(int stackHeight) {
		this.stackHeight = stackHeight;
	}

	public List<ProductType> getProductTypesFromDB() {

		return em.createQuery("SELECT pt FROM ProductType pt",
				ProductType.class).getResultList();
	}

	public List<Process> getProcessesFromDB() {
		return em.createQuery("SELECT p FROM Process p", Process.class)
				.getResultList();
	}

	public List<Stack> getStacksFromDB() {
		return em.createQuery("SELECT s FROM Stack s", Stack.class)
				.getResultList();
	}

	public List<Location> getLocationsFromDB() {
		return em.createQuery("SELECT l FROM Location l", Location.class)
				.getResultList();
	}

	public List<Stack> getFinishedFromDB() {
		return em.createQuery("SELECT s FROM Stack s WHERE s.finished = true",
				Stack.class).getResultList();
	}

	public List<Stack> getWastesFromDB() {
		return em.createQuery("SELECT s FROM Stack s WHERE s.wasted = true",
				Stack.class).getResultList();
	}

	public List<ProductType> getProductTypes() {
		return new ArrayList<ProductType>(productTypes);
	}

	public List<Process> getProcesses() {
		return new ArrayList<Process>(processes);
	}

	public List<Stack> getStacks() {
		return new ArrayList<Stack>(stacks);
	}

	public List<Location> getLocations() {
		return new ArrayList<Location>(locations);
	}

	public List<Stack> getFinished() {
		List<Stack> finished = new ArrayList<Stack>();
		for (Stack x : getStacks()) {
			if (x.isFinished()) {
				finished.add(x);
			}
		}
		return finished;
	}

	public List<Stack> getWastes() {
		List<Stack> wastes = new ArrayList<Stack>();

		for (Stack x : getStacks()) {
			if (x.isWasted()) {
				wastes.add(x);
			}
		}

		return wastes;
	}

	public List<Stack> getRunningStacks() {
		List<Stack> allStacks = getStacks();
		List<Stack> runningStacks = new ArrayList<Stack>();

		for (int i = 0; i < allStacks.size(); i++) {
			if (!allStacks.get(i).isFinished() && !allStacks.get(i).isWasted())
				runningStacks.add(allStacks.get(i));
		}

		return runningStacks;
	}

	/**
	 * Updates local Lists with results form database
	 * 
	 * @author Alexander Shidlovsky
	 */
	public void updateLocalStorage() {
		productTypes = getProductTypesFromDB();
		processes = getProcessesFromDB();
		stacks = getStacksFromDB();
		locations = getLocationsFromDB();
	}

	/**
	 * Stores object in database and updates local lists
	 * 
	 * @param obj
	 *            Object to store in database
	 * @return Stored object
	 * @author Ceslovas Lopan
	 */
	public Object store(Object obj) {
		em.getTransaction().begin();
		em.persist(obj);
		em.getTransaction().commit();
		updateLocalStorage();

		return obj;
	}

	/**
	 * 
	 * @author Alexey Kuprin
	 */
	public void remove(Object obj) {
		em.getTransaction().begin();
		em.remove(obj);
		em.getTransaction().commit();
		updateLocalStorage();
	}

	/**
	 * Marks stack as wasted
	 * 
	 * @param stack
	 *            Wasted stack
	 * @author Alexander Shidlovsky
	 */
	public void putInWastes(Stack stack) {
		stack.setWasted(true);
		stack.setFinished(false);
		stack.removeLocation();
		update(stack);
	}

	/**
	 * Marks stack as finished
	 * 
	 * @param stack
	 *            Finished stack
	 * @author Alexander Shidlovsky
	 */
	public void putInFinished(Stack stack) {
		stack.setFinished(true);
		stack.setWasted(false);
		stack.removeLocation();
		update(stack);
	}

	/**
	 * Updates object in database and updates local lists
	 * 
	 * @param obj
	 *            Object to be updated
	 * @return Updated object
	 * @author Alexander Shidlovsky
	 */
	public Object update(Object obj) {
		em.getTransaction().begin();
		Object o = em.merge(obj);
		em.getTransaction().commit();
		updateLocalStorage();
		return o;
	}
}